package com.sun.mybatisplus.controller;

import com.sun.mybatisplus.entity.BeautyInfo;
import com.sun.mybatisplus.service.BeautyService;
import io.lettuce.core.RedisClient;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.sql.Time;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class RedisController {

    @Autowired
    private BeautyService beautyService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 使用Redisson分布式实现加锁的操作
     *
     * **************************************************************************************
     * 设置睡眠时间为30秒，但是当我们不断的刷新redis中的数据时发现，当时间过了10秒以后，TTL的过期时间又变成了30秒
     * 那么这是什么原因导致的呢？
     * 这是因为Redisson中的看门狗机制来决定的?
     *
     * 看门狗可以对锁进行自动续期：如果业务执行执行的时间过长，不用担心锁的过期问题，看门狗会帮助我们自动的把锁进行续期30秒
     *  myLock.lock(20, TimeUnit.SECONDS);
     *  但是需要注意的是：加入我们设置20秒，业务的执行时间是30秒。这样在20秒后这个锁就会被删除，这时另外一个线程进来，创建一把新的锁
     *  。30秒后业务删除，发现删除的锁不是当初建的那个锁，就会抛出异常。一般来说自定义锁的过期时间应该大于业务执行的时间
     *
     *  我们自定义的时间，传递的时候传递我们自定义的时间20秒，如果我们不传时间就是采用默认的-1
     *
     *  自定义时间时其实是执行一段lua脚本：
     *  **********************************lua脚本**************************************
     *  "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]);
     *  return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]);
     *  return nil; end; return redis.call('pttl', KEYS[1]);"
     *  **********************************lua脚本**************************************
     *
     *  如果我们采用默认的信息，不传递自定义的时间，那么也同样执行lua脚本。但是这个时间时怎么来的呢？
     *  commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
     *  通过上面的代码回去默认配置文件中的锁的过期时间默认值就是30秒； this.lockWatchdogTimeout = 30000L;
     *
     *  不自定传递时间，当我们获取锁是，会同时启动一个定时任务，这个定时任务的作用就是不断的给锁设置过期时间，而新的过期时间就是看门狗默认的过期时间；
     *  那么这个定时任务是多长时间执行一次呢？
     *  internalLockLeaseTime /3 就是默认的看门狗时间的 1/3
     *
     *
     *  那么最佳实战是怎么样的呢？
     *  建议使用：   myLock.lock(30, TimeUnit.SECONDS);
     *  这样省掉了我们的自动续期的操作。既然他默认是30秒那么我们就设置时间为30秒。但是看具体的业务操作，当业务操作耗时过大时，我们就设置的大一点！
     *
     *
     *
     *
     *
     *
     * @return
     */
    @RequestMapping("/lock")
    public String lock() {
        RLock myLock = redissonClient.getLock("my_lock");
        //加锁，默认加的锁都是30秒的时间
        //myLock.lock();

        //但是我们也可以自定义锁的过期时间   ctrl + alt看方法的实现的快捷键
        myLock.lock(20, TimeUnit.SECONDS);  //比如指定20秒


        try {
            System.out.println("加锁成功，执行业务。。。。。" + Thread.currentThread().getId());
            TimeUnit.SECONDS.sleep(30);
        } catch (Exception e) {
        } finally {
            //释放锁
            System.out.println("释放锁！" + Thread.currentThread().getId());
            myLock.unlock();
        }
        return "hello";
    }


    /**
     * 查询所有
     * 使用setnx ex 进行加锁操作
     * 使用lua脚本进行解锁操作
     *
     * @return
     */
    @RequestMapping("/redisAll")
    public List<BeautyInfo> selectAll() {
        String uuid = UUID.randomUUID().toString();
        List<BeautyInfo> list = null;
        //加锁
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 5, TimeUnit.MINUTES);
        if (lock) {
            //执行业务
            try {
                list = beautyService.list();
            } finally {
                //释放锁
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                //执行lua脚本,删除成功返回1，失败返回0
                Long execute = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
                if (execute.equals(1l)) {
                    log.info("删除锁成功！");
                } else {
                    log.error("删除锁失败！");
                }
            }
        } else {
            selectAll(); //重新获取锁，自旋
        }
        return list;
    }


    /**
     * 等5个班全部走完执行关门操作
     * @return
     */
    @RequestMapping("/close")
    public String closeDoor() throws InterruptedException {
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        door.trySetCount(5);
        door.await();
        return "放假了......";
    }


    /**
     * 放学走人
     * @return
     */
    @RequestMapping("/go/{id}")
    public String go(@PathVariable("id") String id){
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        door.countDown();
        return id + "班的人都走了。。。";
    }



    /**
     * 停车位的信号量
     * 停车
     * @return
     */
    @RequestMapping("/park")
    public String park() throws InterruptedException {
        RSemaphore park = redissonClient.getSemaphore("park");
        park.acquire();
        return "ok";
    }


    /**
     * 停车位的信号量
     * 开车走
     * @return
     */
    @RequestMapping("/leave")
    public String leave() throws InterruptedException {
        RSemaphore park = redissonClient.getSemaphore("park");
        park.release();
        return "ok";
    }


}
